/* Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/*
  <file> fdfilter.c </file>
  <brief>
    fdfilter is a collection of API for manipulating raw input.
    Given input by fd, fdfilter reads from it and oupput to another fd, filtering the stream by 'filter-prog'
    The 'filter-prog' is any executable of which input is from stdin and of output is to stdout.
    This file is part of ULS, Unified Lexical Scheme.
  </brief>
  <author>
    Stanley J. Hong <babel96@gmail.com>, 2011.
  </author>
*/

#include "common.h"
#include "fdfilter.h"
#include "uls_util.h"
#include "uls_log.h"

void
fdf_init(fdf_t *fdf, fdf_iprovider_t i_provider, char* cmdline)
{
	int i;

	if (i_provider == NULL)
		i_provider = fdf_iprovider_simple;

	fdf->fd_org = -1;
	fdf->fd = -1;
	fdf->i_provider = i_provider;
	fdf->filter = cmdline;

	for (i=0; i<FDF_N_PROCS_POPEN; i++) {
		fdf->child_pid[i] = -1;
	}
}

void
fdf_reset(fdf_t *fdf, fdf_iprovider_t i_provider, char* cmdline)
{
	if (i_provider == NULL)
		i_provider = fdf_iprovider_simple;

	fdf->i_provider = i_provider;
	fdf->filter = cmdline;
}

void
fdf_deinit(fdf_t *fdf)
{
	fdf->fd_org = -1;
	fdf->fd = -1;
}

static int
fdf_open_0(int *pipe1, int *pipe2, fdf_t *fdf, int fd)
{
	int  r_pipe, w_pipe;
	int  rc;

	if ((fdf->child_pid[0]=fork()) < 0) {
		err_log("fork error!!");
		return -1;
	} 

	if (fdf->child_pid[0] == 0) {
		close(pipe1[1]); close(pipe2[0]);
		r_pipe = pipe1[0]; w_pipe = pipe2[1];
		rc = fdf->i_provider(fd, w_pipe);
		exit(rc);
	}

	fdf->child_pid[1] = -1;

	close(pipe1[0]); close(pipe2[1]);
	r_pipe = pipe2[0]; w_pipe = pipe1[1];

	close(w_pipe);
	fdf->fd_org = fd;
	fdf->fd = r_pipe; // r_pipe instead of fd

	// r_pipe >= 0
	return r_pipe;
}

int
fdf_open(fdf_t *fdf, int fd)
{
	char* cmdline = fdf->filter;
	int  pipe1[2], pipe2[2];
	int  r_pipe, w_pipe, rc;

	if (fd < 0) return -1;

	if (pipe(pipe1) < 0) {
		err_log("can't create pipes");
		return -1;
	}

	if (pipe(pipe2) < 0) {
		err_log("can't create pipes");
		close(pipe1[0]); close(pipe1[1]);
		return -1;
	}

	if (cmdline == NULL) {
		if ((r_pipe=fdf_open_0(pipe1, pipe2, fdf, fd)) < 0) {
			close(pipe1[0]); close(pipe1[1]);
			close(pipe2[0]); close(pipe2[1]);
		}
		return r_pipe;
	}

	// assert: cmdline != NULL

	if ((fdf->child_pid[1]=fork()) < 0) {
		err_log("fork error!!");
		close(pipe1[0]); close(pipe1[1]);
		close(pipe2[0]); close(pipe2[1]);
		return -1;
	}

	if (fdf->child_pid[1] == 0) {
		close(fd); // fd is unnecessary

		close(pipe1[1]); close(pipe2[0]);
		r_pipe = pipe1[0]; w_pipe = pipe2[1];

	 	if (r_pipe != STDIN_FILENO) {
			if (dup2(r_pipe, STDIN_FILENO) != STDIN_FILENO ) {
				err_log("dup2 error (stdin)!!\n");
				exit(1);
			}
			close(r_pipe);
        }

		if (w_pipe != STDOUT_FILENO) {
			if (dup2(w_pipe, STDOUT_FILENO) != STDOUT_FILENO ) {
				err_log("dup2 error (stdout)!!\n");
				exit(1);
			}
			close(w_pipe);
		}

		if (uls_execv_cmdline(cmdline) < 0) {
			err_log("execle error\n");
			exit(1);
		}
		// NEVER REACHED
	}

	// child_pid[1] > 0
	close(pipe1[0]); close(pipe2[1]);
	r_pipe = pipe2[0]; w_pipe = pipe1[1];

	if ((fdf->child_pid[0]=fork()) < 0) {
		err_log("fork error!!");
		close(r_pipe); close(w_pipe);
		return -1;
	}

	if (fdf->child_pid[0] == 0) {
		close(r_pipe);
		rc = fdf->i_provider(fd, w_pipe);
		close(fd);
		close(w_pipe);
		exit(rc);
	}

	fdf->fd_org = fd; // fd was also transferred to child_pid[0]
	close(w_pipe);

	fdf->fd = r_pipe; // r_pipe instead of fd

	return r_pipe;
}

int
fdf_close(fdf_t* fdf)
{
	fdf->fd_org = -1;

	if (fdf->fd >= 0) {
		close(fdf->fd);
		fdf->fd = -1;
	}

	if (uls_proc_join(fdf->child_pid, FDF_N_PROCS_POPEN) < 0)
		return -1;

	return 0;
}

int
fdf_iprovider_simple(int fdin, int writefd)
{
	int	    rc, stat = 1;
	uls_char_t buff[1024];

	while (stat > 0) {
		if ((rc=uls_readn(fdin, buff, sizeof(buff))) < 0) {
			stat = -1;
			break;
		} else if (rc < sizeof(buff)) {
			stat = 0;
		}

		if (uls_writen(writefd, buff, rc) < rc) {
			stat = -2;
		}
	}

	return stat;
}

int
fdf_iprovider_filelist(int fd_list, int writefd)
{
	char linebuff[ULS_FILEPATH_MAX+1];
	char *lptr, *fpath;
	int  len, rc, stat = 0;
	int  fdin;
	FILE *fp_list;

	if ((fp_list=uls_fdopen(fd_list, "r")) == NULL) {
		err_log("%s: fail to fdopen!", __FUNCTION__);
		return -1;
	}

	while (fgets(linebuff, sizeof(linebuff), fp_list) != NULL) {
		if ((len=strlen(linebuff))>0 && linebuff[len-1]=='\n') {
			linebuff[--len] = '\0';
		}

		lptr = skip_space_chars(linebuff);
		if (*lptr=='\0' || *lptr=='#') continue;

		len -= (int) (lptr - linebuff);
		fpath = lptr;
		fpath[len] = '\0';

		if ((fdin=uls_open(fpath, O_RDONLY | _O_BINARY)) < 0) {
			stat = -1;
			break;
		}

		rc = fdf_iprovider_simple(fdin, writefd);
		close(fdin);

		if (rc < 0) {
			stat = -1;
			break;
		}
	}

	return stat;
}

char**
uls_pars_cmdline(char* cmdline, char** p_line, int* p_args)
{
	int n_alloc_args, k;
	char *lptr, **args;

	if (cmdline == NULL || *(cmdline=skip_space_chars(cmdline)) == '\0') {
		*p_line = NULL;
		*p_args = 0;
		return NULL;
	}

	k = strlen(cmdline);

	*p_line = lptr = (char *) uls_malloc((k+1)*sizeof(char));
	strcpy(lptr, cmdline);

	n_alloc_args = 32;
	args = (char **) uls_malloc((n_alloc_args+1)*sizeof(char *));

	k = uls_get_column_words(lptr, ' ', args, n_alloc_args);
	args[k] = NULL;

	if (p_args != NULL) *p_args = k;
	return args;
}

int
uls_execv_cmdline(char* cmdline)
{
	char *progpath, **args, *argsbuff;
	int  n_args;

	if ((args=uls_pars_cmdline(cmdline, &argsbuff, &n_args)) == NULL) {
		// cmdline == "" or NULL
		return -1;
	}

	// args != NULL;
	progpath = args[0];
	args[0] = uls_filename(progpath);

	if (uls_file_exist(progpath) <= 0 || execv(progpath, args) < 0) {
		uls_mfree(args);
        uls_mfree(argsbuff);
        return -1;
	}

	// NEVER REACHED
	return 0;
}

int
uls_proc_join_round_1(uls_pid_t *child_pid, int n_child_pid)
{
	int status, exit_code;
	int k, stat, n_procs_rem=0;
	uls_pid_t pid;

	for (k=0; k<n_child_pid; k++) {
		if (child_pid[k] <= 0) continue;

		if ((pid=waitpid(child_pid[k], &status, WNOHANG)) < 0) {
			if (errno == EINTR) {
				continue;
			} else if (errno == ECHILD) {
				for (k=0; k<n_child_pid; k++) {
					if (child_pid[k] > 0) child_pid[k] = 0;
				}
				n_procs_rem = 0;
				break;
			} else {
				err_log("error to waitpid()");
				return -1;
			}

		} else if (pid == 0) {
			++n_procs_rem;
			continue;
		}

		stat = -1;
		if (WIFEXITED(status)) {
			exit_code = WEXITSTATUS(status);
			if (exit_code != 0) {
				err_log("child %u exited with %d", pid, exit_code);
				child_pid[k] = -1;
			} else {
				child_pid[k] = 0;
				stat = 0;
			}
		} else {
			child_pid[k] = -1;
		}

		if (stat < 0) return -1;
	}

	return n_procs_rem;
}

int
uls_proc_join(uls_pid_t *child_pid, int n_child_pid)
{
	int rc, stat=0;

	for ( ; ; ) {
		if ((rc=uls_proc_join_round_1(child_pid, n_child_pid)) < 0) {
			stat = -1;
			break;
		} else if (rc == 0) {
			break;
		}

		usleep(200000); // sleeping after 1-rounding, ...
	}

	return stat;
}
